/**
 * matrix44.js
 * 
 * Implement 4x4 matrix class.
 * Requires float4.js
 * 
 * @author A.Weissflog
 */

/**
 * The default constructor, initializes components to the identity matrix.
 */
function Matrix44()
{
	this.r0 = Float4.FromValues(1.0, 0.0, 0.0, 0.0);
	this.r1 = Float4.FromValues(0.0, 1.0, 0.0, 0.0);
	this.r2 = Float4.FromValues(0.0, 0.0, 1.0, 0.0);
	this.r3 = Float4.FromValues(0.0, 0.0, 0.0, 1.0);
}

/**
 * Create Matrix44 object from row vectors.
 * 
 * @param {Float4} row0
 * @param {Float4} row1
 * @param {Float4} row2
 * @param {Float4} row3
 */
Matrix44.FromRows = function(row0, row1, row2, row3)
{
	var m = new Matrix44();	
	m.r0 = Float4.FromFloat4(row0);
	m.r1 = Float4.FromFloat4(row1);
	m.r2 = Float4.FromFloat4(row2);
	m.r3 = Float4.FromFloat4(row3);
	return m;
}

/**
 * Create Matrix44 object from values.
 * 
 * @param {Number} r0x
 * @param {Number} r0y
 * @param {Number} r0z
 * @param {Number} r0w
 * @param {Number} r1x
 * @param {Number} r1y
 * @param {Number} r1z
 * @param {Number} r1w
 * @param {Number} r2x
 * @param {Number} r2y
 * @param {Number} r2z
 * @param {Number} r2w
 * @param {Number} r3x
 * @param {Number} r3y
 * @param {Number} r3z
 * @param {Number} r3w
 */
Matrix44.FromValues = function(r0x, r0y, r0z, r0w,
                                     r1x, r1y, r1z, r1w,
									 r2x, r2y, r2z, r2w,
									 r3x, r3y, r3z, r3w)
{
	var m = new Matrix44();
	m.r0.Set(r0x, r0y, r0z, r0w);
	m.r1.Set(r1x, r1y, r1z, r1w);
	m.r2.Set(r2x, r2y, r2z, r2w);
	m.r3.Set(r3x, r3y, r3z, r3w);
	return m;
}

/**
 * Copy constructor.
 * 
 * @param {Matrix44} rhs
 */
Matrix44.FromMatrix44 = function(rhs)
{
	var m = Matrix44.FromRows(rhs.r0, rhs.r1, rhs.r2, rhs.r3);
	return m;
}

/**
 * Compare 2 matrices with tolerance value.
 * 
 * @param {Matrix44} m0
 * @param {Matrix44} m1
 * @param {Number} tol
 */
Matrix44.Equal = function(m0, m1, tol)

{
	return Float4.Equal(m0.r0, m1.r0, tol) &&
	       Float4.Equal(m0.r1, m1.r1, tol) &&
		   Float4.Equal(m0.r2, m1.r2, tol) &&
		   Float4.Equal(m0.r3, m1.r3, tol);
}

/**
 * Return content as string.
 */
Matrix44.prototype.AsString = function()
{
    return "{r0x: " + this.r0.x + ", r0y: " + this.r0.y + ", r0z: " + this.r0.z + ", r0w: " + this.r0.w +
		   " r1x: " + this.r1.x + ", r1y: " + this.r1.y + ", r1z: " + this.r1.z + ", r1w: " + this.r1.w +
           " r2x: " + this.r2.x + ", r2y: " + this.r2.y + ", r2z: " + this.r2.z + ", r2w: " + this.r2.w +
           " r3x: " + this.r3.x + ", r3y: " + this.r3.y + ", r3z: " + this.r3.z + ", r3w: " + this.r3.w + "}";	
}

/**
 * Set content from row vectors.
 * 
 * @param {Float4} row0
 * @param {Float4} row1
 * @param {Float4} row2
 * @param {Float4} row3
 */
Matrix44.prototype.SetRows = function(row0, row1, row2, row3)
{
	this.r0 = Float4.FromFloat4(row0);
	this.r1 = Float4.FromFloat4(row1);
	this.r2 = Float4.FromFloat4(row2);
	this.r3 = Float4.FromFloat4(row3);
}

/**
 * Inplace-translate the matrix.
 * 
 * @param {Number} x
 * @param {Number} y
 * @param {Number} z
 */
Matrix44.prototype.Translate = function(x, y, z)
{
	this.r3.x += x;
	this.r3.y += y;
	this.r3.z += z;
}

/**
 * Inplace-scale the matrix.
 * 
 * @param {Number} s
 */
Matrix44.prototype.Scale = function(s)
{
	this.r0.x *= s; this.r0.y *= s; this.r0.z *= s;
	this.r1.x *= s; this.r1.y *= s; this.r1.z *= s;
	this.r2.x *= s; this.r2.y *= s; this.r2.z *= s;
}

/**
 * Transform vector by matrix.
 * 
 * @param {Float4} v
 * @param {Matrix44} m
 * @return {Float4}
 */
Matrix44.Transform = function(v, m)
{
	return Float4.FromValues(m.r0.x * v.x + m.r1.x * v.y + m.r2.x * v.z + m.r3.x * v.w, 
                                   m.r0.y * v.x + m.r1.y * v.y + m.r2.y * v.z + m.r3.y * v.w, 
                                   m.r0.z * v.x + m.r1.z * v.y + m.r2.z * v.z + m.r3.z * v.w, 
                                   m.r0.w * v.x + m.r1.w * v.y + m.r2.w * v.z + m.r3.w * v.w);
}

/**
 * In-place matrix multiply.
 * @param {Matrix44} rhs
 */
Matrix44.prototype.Multiply = function(rhs)
{
	this.r0 = Matrix44.Transform(this.r0, rhs);
	this.r1 = Matrix44.Transform(this.r1, rhs);
	this.r2 = Matrix44.Transform(this.r2, rhs);
	this.r3 = Matrix44.Transform(this.r3, rhs);
}

/**
 * Multiply 2 matrices, return result in new matrix.
 * @param {Matrix44} m0
 * @param {Matrix44} m1
 * @return {Matrix44}
 */
Matrix44.Multiply = function(m0, m1)
{
	var m = Matrix44.FromMatrix44(m0);
	m.Multiply(m1);
	return m;
}

/**
 * Set matrix to identity.
 */
Matrix44.prototype.Identity = function()
{
	this.r0.Set(1.0, 0.0, 0.0, 0.0);
	this.r1.Set(0.0, 1.0, 0.0, 0.0);
	this.r2.Set(0.0, 0.0, 1.0, 0.0);
	this.r3.Set(0.0, 0.0, 0.0, 1.0);
}

/**
 * Compute determinant of matrix.
 * 
 * @return {Number}
 */
Matrix44.prototype.Determinant = function()
{
	var det01 = this.r2.x * this.r3.y - this.r3.x * this.r2.y;
    var det02 = this.r2.x * this.r3.z - this.r3.x * this.r2.z;
    var det12 = this.r2.y * this.r3.z - this.r3.y * this.r2.z;
    var det13 = this.r2.y * this.r3.w - this.r3.y * this.r2.w;
    var det23 = this.r2.z * this.r3.w - this.r3.z * this.r2.w;
    var det30 = this.r2.w * this.r3.x - this.r3.w * this.r2.x;

    var tx =  (this.r1.y * det23 - this.r1.z * det13 + this.r1.w * det12);
    var ty = -(this.r1.x * det23 + this.r1.z * det30 + this.r1.w * det02);
    var tz =  (this.r1.x * det13 + this.r1.y * det30 + this.r1.w * det01);
    var tw = -(this.r1.x * det12 - this.r1.y * det02 + this.r1.z * det01);
	
    return this.r0.x * tx + this.r1.x * ty + this.r2.x * tz * this.r3.x * tw;
}

/**
 * Compute inverse of matrix.
 * 
 * @return {Matrix44}
 */
Matrix44.Inverse = function(rhs)
{
    // 2x2 sub matrix determinants
    var det01 = rhs.r2.x*rhs.r3.y - rhs.r3.x*rhs.r2.y;
    var det02 = rhs.r2.x*rhs.r3.z - rhs.r3.x*rhs.r2.z;
    var det12 = rhs.r2.y*rhs.r3.z - rhs.r3.y*rhs.r2.z;
    var det13 = rhs.r2.y*rhs.r3.w - rhs.r3.y*rhs.r2.w;
    var det23 = rhs.r2.z*rhs.r3.w - rhs.r3.z*rhs.r2.w;
    var det30 = rhs.r2.w*rhs.r3.x - rhs.r3.w*rhs.r2.x;

 	var t = new Matrix44();
    t.r0.x =  (rhs.r1.y*det23 - rhs.r1.z*det13 + rhs.r1.w*det12);
    t.r1.x = -(rhs.r1.x*det23 + rhs.r1.z*det30 + rhs.r1.w*det02);
    t.r2.x =  (rhs.r1.x*det13 + rhs.r1.y*det30 + rhs.r1.w*det01);
    t.r3.x = -(rhs.r1.x*det12 - rhs.r1.y*det02 + rhs.r1.z*det01);

    var det = rhs.r0.x*t.r0.x + rhs.r1.x*t.r1.x + rhs.r2.x*t.r2.x * rhs.r3.x*t.r3.x;
    if (0 == det)
    {
        // matrix non invertible
		return new Matrix44();
    }

    var invDet = 1.0 / det;

    t.r0.x *= invDet;
    t.r1.x *= invDet;
    t.r2.x *= invDet;
    t.r3.x *= invDet;

    t.r0.y = -(rhs.r0.y*det23 - rhs.r0.z*det13 + rhs.r0.w*det12) * invDet;
    t.r1.y =  (rhs.r0.x*det23 + rhs.r0.z*det30 + rhs.r0.w*det02) * invDet;
    t.r2.y = -(rhs.r0.x*det13 + rhs.r0.y*det30 + rhs.r0.w*det01) * invDet;
    t.r3.y =  (rhs.r0.x*det12 - rhs.r0.y*det02 + rhs.r0.z*det01) * invDet;

    det01 = rhs.r0.x*rhs.r1.y - rhs.r1.x*rhs.r0.y;
    det02 = rhs.r0.x*rhs.r1.z - rhs.r1.x*rhs.r0.z;
    det12 = rhs.r0.y*rhs.r1.z - rhs.r1.y*rhs.r0.z;
    det13 = rhs.r0.y*rhs.r1.w - rhs.r1.y*rhs.r0.w;
    det23 = rhs.r0.z*rhs.r1.w - rhs.r1.z*rhs.r0.w;
    det30 = rhs.r0.w*rhs.r1.x - rhs.r1.w*rhs.r0.x;

    t.r0.z =  (rhs.r3.y*det23 - rhs.r3.z*det13 + rhs.r3.w*det12) * invDet;
    t.r1.z = -(rhs.r3.x*det23 + rhs.r3.z*det30 + rhs.r3.w*det02) * invDet;
    t.r2.z =  (rhs.r3.x*det13 + rhs.r3.y*det30 + rhs.r3.w*det01) * invDet;
    t.r3.z = -(rhs.r3.x*det12 - rhs.r3.y*det02 + rhs.r3.z*det01) * invDet;

    t.r0.w = -(rhs.r2.y*det23 - rhs.r2.z*det13 + rhs.r2.w*det12) * invDet;
    t.r1.w =  (rhs.r2.x*det23 + rhs.r2.z*det30 + rhs.r2.w*det02) * invDet;
    t.r2.w = -(rhs.r2.x*det13 + rhs.r2.y*det30 + rhs.r2.w*det01) * invDet;
    t.r3.w =  (rhs.r2.x*det12 - rhs.r2.y*det02 + rhs.r2.z*det01) * invDet;
	return t;
}

/**
 * Create right-handed lookat matrix.
 * 
 * @param {Float4} eye
 * @param {Float4} at
 * @param {Float4} up
 * @return {Matrix44}
 */
Matrix44.LookatRh = function(eye, at, up)
{
	var zAxis = Float4.Normalize(Float4.Sub(at, eye));
	var normUp = Float4.Normalize(up);
	if (Math.abs(Float4.Dot3(zAxis, normUp)) > 0.999999) {
		// need to choose a different up vector because up and lookat point
		// into same or opposite direction
		// just rotate y->x, x->z and z->y
		var newX = normUp.y;
		var newY = normUp.z;
		var newZ = normUp.x;
		normUp.x = newX;
		normUp.y = newY;
		normUp.z = newZ;
	}
	var xAxis = Float4.Normalize(Float4.Cross3(zAxis, normUp));
	var yAxis = Float4.Cross3(xAxis, zAxis);
	
	return Matrix44.FromRows(xAxis, yAxis, zAxis, eye);
}

/**
 * Create right-handed orthogonal projection matrix.
 * 
 * @param {Number} w
 * @param {Number} h
 * @param {Number} zn
 * @param {Number} zf
 * @return {Matrix44}
 */
Matrix44.OrthoRh = function(w, h, zn, zf)
{
	var row0 = Float4.FromValues(2.0 / w, 0.0, 0.0, 0.0);
	var row1 = Float4.FromValues(0.0, 2.0 / h, 0.0, 0.0);
	var row2 = Float4.FromValues(0.0, 0.0, 1.0 / (zn - zf), 0.0);
	var row3 = Float4.FromValues(0.0, 0.0, row2.z * zn, 1.0);
	return Matrix44.FromRows(row0, row1, row2, row3);
}

/**
 * Create right-handed perspective projection matrix from vertical field-of-view angle.
 * 
 * @param {Number} fovy
 * @param {Number} aspect
 * @param {Number} zn
 * @param {Number} zf
 * @return {Matrix44}
 */
Matrix44.PerspFovRh = function(fovy, aspect, zn, zf)
{
	var yScale = 1.0 / Math.tan(fovy / 2.0);
	var xScale = yScale / aspect;
	
	var row0 = Float4.FromValues(xScale, 0.0, 0.0, 0.0);
	var row1 = Float4.FromValues(0.0, yScale, 0.0, 0.0);
	var row2 = Float4.FromValues(0.0, 0.0, zf / (zn-zf), -1.0);
	var row3 = Float4.FromValues(0.0, 0.0, zn * row2.z, 0.0);

	return Matrix44.FromRows(row0, row1, row2, row3);	
}

/**
 * Create right-handed perspective projection matrix from width and height
 * on near-plane.
 * 
 * @param {Number} w
 * @param {Number} h
 * @param {Number} zn
 * @param {Number} zf
 * @return {Matrix44}
 */
Matrix44.PerspRh = function(w, h, zn, zf)
{
	var zn2 = zn * 2.0;
	
	var row0 = Float4.FromValues(zn2 / w, 0.0, 0.0, 0.0);
	var row1 = Float4.FromValues(0.0, zn2 / h, 0.0, 0.0);
	var row2 = Float4.FromValues(0.0, 0.0, zf / (zn - zf), -1.0);
	var row3 = Float4.FromValues(0.0, 0.0, row2.z * zn, 0.0);
	
	return Matrix44.FromRows(row0, row1, row2, row3);
}

/**
 * Create rotation matrix from rotation axis and angle.
 * 
 * @param {Float4} axis
 * @param {Number} angle
 * @return {Matrix44}
 */
Matrix44.RotationAxis = function(axis, angle)
{
	var v = Float4.Normalize(axis);
	var s = Math.sin(angle);
	var c = Math.cos(angle);
	var t = 1.0 - c;
	var x = v.x;
	var y = v.y;
	var z = v.z;
	
	var row0 = Float4.FromValues((t * x * x) + c, (t * x * y) + (s * z), (t * x * z) - (s * y), 0.0);
	var row1 = Float4.FromValues((t * x * y) - (s * z), (t * y * y) + c, (t * y * z) + (s * x), 0.0);
	var row2 = Float4.FromValues((t * x * z) + (s * y), (t * y * z) - (s * x), (t * z * z) + c, 0.0);
	var row3 = Float4.FromValues(0.0, 0.0, 0.0, 1.0);
	
	return Matrix44.FromRows(row0, row1, row2, row3);
}

/**
 * Create rotation matrix from rotation around x-axis.
 * 
 * @param {Number} angle
 * @return {Matrix44}
 */
Matrix44.RotationX = function(angle)
{
	return Matrix44.RotationAxis(Float4.FromValues(1.0, 0.0, 0.0, 0.0), angle);
}

/**
 * Create rotation matrix from rotation around y-axis.
 * 
 * @param {Number} angle
 * @return {Matrix44}
 */
Matrix44.RotationY = function(angle)
{
	return Matrix44.RotationAxis(Float4.FromValues(0.0, 1.0, 0.0, 0.0), angle);
}

/**
 * Create rotation matrix from rotation around z-axis.
 * 
 * @param {Number} angle
 * @return {Matrix44}
 */
Matrix44.RotationZ = function(angle)
{
	return Matrix44.RotationAxis(Float4.FromValues(0.0, 0.0, 1.0, 0.0), angle);
}

/**
 * Create rotation matrix from yaw-pitch-roll angles.
 * 
 * @param {Number} yaw
 * @param {Number} pitch
 * @param {Number} roll
 * @return {Matrix44}
 */
Matrix44.RotationYawPitchRoll = function(yaw, pitch, roll)
{
	var rotateYPR = Matrix44.RotationZ(roll);
	rotateYPR = Matrix44.Multiply(rotateYPR, Matrix44.RotationX(pitch));
	rotateYPR = Matrix44.Multiply(rotateYPR, Matrix44.RotationY(yaw));
	return rotateYPR;
}

/**
 * Create scaling matrix
 * @param {Number} sx
 * @param {Number} sy
 * @param {Number} sz
 * @return {Matrix44}
 */
Matrix44.Scaling = function(sx, sy, sz)
{
	return Matrix44.FromValues(sx, 0.0, 0.0, 0.0, 
								     0.0, sy, 0.0, 0.0, 
								     0.0, 0.0, sz, 0.0, 
								     0.0, 0.0, 0.0, 1.0);
}

/**
 * Create translation matrix.
 * 
 * @param {Number} x
 * @param {Number} y
 * @param {Number} z
 * @return {Matrix44}
 */
Matrix44.Translation = function(x, y, z)
{
	return Matrix44.FromValues(1.0, 0.0, 0.0, 0.0,
								     0.0, 1.0, 0.0, 0.0,
								     0.0, 0.0, 1.0, 0.0,
								     x,   y,   z,   1.0);
}

/**
 * Create transposed matrix.
 * 
 * @param {Matrix44} rhs
 * @return {Matrix44}
 */
Matrix44.Transpose = function(rhs)
{
	return Matrix44.FromValues(rhs.r0.x, rhs.r1.x, rhs.r2.x, rhs.r3.x,
								     rhs.r0.y, rhs.r1.y, rhs.r2.y, rhs.r3.y,
								     rhs.r0.z, rhs.r1.z, rhs.r2.z, rhs.r3.z,
								     rhs.r0.w, rhs.r1.w, rhs.r2.w, rhs.r3.w);
}

/**
 * Create rotation matrix from Quaternion
 * 
 * @param {Quaternion} q
 * @return {Matrix44}
 */
Matrix44.RotationQuaternion = function(q)
{
	var x2 = q.x + q.x;
	var y2 = q.y + q.y;
	var z2 = q.z + q.z;
	
	var xx = q.x * x2;
	var xy = q.x * y2;
	var xz = q.x * z2;
	
	var yy = q.y * y2;
	var yz = q.y * z2;
	var zz = q.z * z2;
	
	var wx = q.w * x2;
	var wy = q.w * y2;
	var wz = q.w * z2;
	
	var row0 = Float4.FromValues(1.0 - (yy + zz), xy + wz, xz - wy, 0.0);
	var row1 = Float4.FromValues(xy - wz, 1.0 - (xx + zz), yz + wx, 0.0);
	var row2 = Float4.FromValues(xz + wy, yz - wx, 1.0 - (xx + yy), 0.0);
	var row3 = Float4.FromValues(0.0, 0.0, 0.0, 1.0);
	
	return Matrix44.FromRows(row0, row1, row2, row3);
}

/**
 * Create transform matrix from scaling, rotation and translation.
 * 
 * @param {Float4} scalingCenter
 * @param {Quaternion} scalingRotation
 * @param {Float4} scaling
 * @param {Float4} rotationCenter
 * @param {Quaternion} rotation
 * @param {Float4} translation
 */
Matrix44.Transformation = function(scalingCenter, scalingRotation, scaling, rotationCenter, rotation, translation)
{
	var scaleCenterNegative = Matrix44.Translation(-scalingCenter.x, -scalingCenter.y, -scalingCenter.z);
	var scaleRotation = Matrix44.RotationQuaternion(scalingRotation);
	var scaleRotationT = Matrix44.Transpose(scaleRotation);
	
	var scale = Matrix44.Scaling(scaling.x, scaling.y, scaling.z);
	var rot = Matrix44.RotationQuaternion(rotation);
	
	var res = Matrix44.Multiply(scaleCenterNegative, scaleRotationT);
	res.Multiply(scale);
	res.Multiply(scaleRotation);
	
	res.r3.Add(Float4.Sub(scalingCenter, rotationCenter));
	res.Multiply(rot);
	res.r3.Add(Float4.Add(rotationCenter, translation));
	
	return res;
}









